#ifndef BINARY_EXPRESSION_NODE_H
#define BINARY_EXPRESSION_NODE_H

#include "ExpressionNode.h"

template <class BinaryOperator>
class BinaryExpressionNode: public ExpressionNode {
	public:
		BinaryExpressionNode(ExpressionNode* leftChild, ExpressionNode* rightChild):
				_leftChild(leftChild),
				_rightChild(rightChild) {}

		~BinaryExpressionNode() {
			delete _leftChild;
			delete _rightChild;
		}

		virtual Column pull(const SharedArray<Column>& data) {
			Column inL = _leftChild->pull(data);
			Column inR = _rightChild->pull(data);
			assert(inL.size() == inR.size());

			BinaryOperator op;
			Type inLType = inL.type();
			Type inRType = inR.type();
			Type outType = op.outType(inLType, inRType);

			switch (inLType) {
				case INT:
					switch (inRType) {
						case INT:
							switch (outType) {
								case INT: return compute<int32_t, int32_t, int32_t>(inL, inR);
								case DOUBLE: return compute<int32_t, int32_t, double>(inL, inR);
								case BOOL: return compute<int32_t, int32_t, bool>(inL, inR);
								default: assert(false);
							}

						case DOUBLE:
							switch (outType) {
								case INT: return compute<int32_t, double, int32_t>(inL, inR);
								case DOUBLE: return compute<int32_t, double, double>(inL, inR);
								case BOOL: return compute<int32_t, double, bool>(inL, inR);
								default: assert(false);
							}

						case BOOL:
							switch (outType) {
								case INT: return compute<int32_t, bool, int32_t>(inL, inR);
								case DOUBLE: return compute<int32_t, bool, double>(inL, inR);
								case BOOL: return compute<int32_t, bool, bool>(inL, inR);
								default: assert(false);
							}

						default:
							assert(false);
					}

				case DOUBLE:
					switch (inRType) {
						case INT:
							switch (outType) {
								case INT: return compute<double, int32_t, int32_t>(inL, inR);
								case DOUBLE: return compute<double, int32_t, double>(inL, inR);
								case BOOL: return compute<double, int32_t, bool>(inL, inR);
								default: assert(false);
							}

						case DOUBLE:
							switch (outType) {
								case INT: return compute<double, double, int32_t>(inL, inR);
								case DOUBLE: return compute<double, double, double>(inL, inR);
								case BOOL: return compute<double, double, bool>(inL, inR);
								default: assert(false);
							}

						case BOOL:
							switch (outType) {
								case INT: return compute<double, bool, int32_t>(inL, inR);
								case DOUBLE: return compute<double, bool, double>(inL, inR);
								case BOOL: return compute<double, bool, bool>(inL, inR);
								default: assert(false);
							}

						default:
							assert(false);
					}

				case BOOL:
					switch (inRType) {
						case INT:
							switch (outType) {
								case INT: return compute<bool, int32_t, int32_t>(inL, inR);
								case DOUBLE: return compute<bool, int32_t, double>(inL, inR);
								case BOOL: return compute<bool, int32_t, bool>(inL, inR);
								default: assert(false);
							}

						case DOUBLE:
							switch (outType) {
								case INT: return compute<bool, double, int32_t>(inL, inR);
								case DOUBLE: return compute<bool, double, double>(inL, inR);
								case BOOL: return compute<bool, double, bool>(inL, inR);
								default: assert(false);
							}

						case BOOL:
							switch (outType) {
								case INT: return compute<bool, bool, int32_t>(inL, inR);
								case DOUBLE: return compute<bool, bool, double>(inL, inR);
								case BOOL: return compute<bool, bool, bool>(inL, inR);
								default: assert(false);
							}

						default:
							assert(false);
					}

				default:
					assert(false);
			}

			return Column();
		}

	private:
		template <typename TinL, typename TinR, typename Tout>
		Column compute(Column inL, Column inR) {
			SharedArray<Tout> out(inL.size());
			BinaryOperator op;

			for (int i = 0; i < inL.size(); ++i) {
				out[i] = op.compute<TinL, TinR, Tout>(inL.at<TinL>(i), inR.at<TinR>(i));
			}

			return Column(out);
		}

		ExpressionNode* _leftChild;
		ExpressionNode* _rightChild;
};

#endif
